package org.jkuang.qstar.commons.jni;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;


final class LibLoader {

	public static void loadLibrary(String JNI_LIBNAME) {
		if (tryLoadLibrary(JNI_LIBNAME)) {
			return;
		}
		final String jniLibName = System.mapLibraryName(JNI_LIBNAME);
		final String jniResourceName = makeResourceName(jniLibName);
		log("jniResourceName: " + jniResourceName);
		final InputStream jniResource = LibLoader.class.getClassLoader().getResourceAsStream(jniResourceName);

		if (jniResource == null) {
			throw new UnsatisfiedLinkError(String.format(
					"Cannot find "+JNI_LIBNAME+" native library for OS: %s, architecture: %s. "
							+ " for possible solutions (such as building the library from source). Additional"
							+ " information on attempts to find the native library can be obtained by adding"
							+ " org.jkuang.NativeLibrary.DEBUG=1 to the system properties of the JVM.",
					os(), architecture()));
		}
		try {
			final File tempPath = createTemporaryDirectory();
			tempPath.deleteOnExit();
			final String tempDirectory = tempPath.toString();
			System.load(extractResource(jniResource, jniLibName, tempDirectory));
		} catch (IOException e) {
			throw new UnsatisfiedLinkError(
					String.format("Unable to extract native library into a temporary file (%s)", e.toString()));
		}
	}

	private static boolean tryLoadLibrary(String JNI_LIBNAME) {
		try {
			System.loadLibrary(JNI_LIBNAME);
			return true;
		} catch (UnsatisfiedLinkError e) {
			log("tryLoadLibraryFailed: " + e.getMessage());
			return false;
		}
	}

	private static String extractResource(InputStream resource, String resourceName, String extractToDirectory)
			throws IOException {
		final File dst = new File(extractToDirectory, resourceName);
		dst.deleteOnExit();
		final String dstPath = dst.toString();
		log("extracting native library to: " + dstPath);
		final long nbytes = copy(resource, dst);
		System.out.println("copy so to " + dst);
		log(String.format("copied %d bytes to %s", nbytes, dstPath));
		return dstPath;
	}

	private static String os() {
		final String p = System.getProperty("os.name").toLowerCase();
		if (p.contains("linux")) {
			return "linux";
		} else if (p.contains("os x") || p.contains("darwin")) {
			return "darwin";
		} else if (p.contains("windows")) {
			return "windows";
		} else {
			return p.replaceAll("\\s", "");
		}
	}

	private static String architecture() {
		final String arch = System.getProperty("os.arch").toLowerCase();
		return (arch.equals("amd64")) ? "x86_64" : arch;
	}

	private static void log(String msg) {
		System.err.println("NativeLibrary: " + msg);
	}

	private static String makeResourceName(String baseName) {
		String path = LibLoader.class.getPackage().getName().replace('.', '/')+"/native/";
		return path + String.format("%s-%s/", os(), architecture()) + baseName;
	}

	private static long copy(InputStream src, File dstFile) throws IOException {
		FileOutputStream dst = new FileOutputStream(dstFile);
		try {
			byte[] buffer = new byte[1 << 20]; // 1MB
			long ret = 0;
			int n = 0;
			while ((n = src.read(buffer)) >= 0) {
				dst.write(buffer, 0, n);
				ret += n;
			}
			return ret;
		} finally {
			dst.close();
			src.close();
		}
	}

	// Shamelessly adapted from Guava to avoid using java.nio, for Android API
	// compatibility.
	private static File createTemporaryDirectory() {
		File baseDirectory = new File(System.getProperty("java.io.tmpdir"));
		String directoryName = "qstardb_native_libraries-" + System.currentTimeMillis() + "-";
		for (int attempt = 0; attempt < 1000; attempt++) {
			File temporaryDirectory = new File(baseDirectory, directoryName + attempt);
			if (temporaryDirectory.mkdir()) {
				return temporaryDirectory;
			}
		}
		throw new IllegalStateException("Could not create a temporary directory (tried to make " + directoryName
				+ "*) to extract QStarDB native libraries.");
	}

	private LibLoader() {
	}
}
